package com.hexb.smh.service.impl;

import com.hexb.core.common.Page;
import com.hexb.core.exception.BusinessException;
import com.hexb.core.model.BaseErrorCodes;
import com.hexb.core.service.Pagination;
import com.hexb.core.utils.Assert;
import com.hexb.smh.config.SysConfig;
import com.hexb.smh.config.shiro.BCryptCredentialsMatcher;
import com.hexb.smh.entity.Account;
import com.hexb.smh.entity.enums.RoleType;
import com.hexb.smh.entity.param.account.AccountParam;
import com.hexb.smh.entity.param.account.AccountQueryParam;
import com.hexb.smh.entity.view.ImportResult;
import com.hexb.smh.entity.view.SimpleAccount;
import com.hexb.smh.errors.ErrorCodes;
import com.hexb.smh.mapper.AccountMapper;
import com.hexb.smh.service.IAccountService;
import com.hexb.smh.utils.AccountHelper;
import com.hexb.smh.utils.IPasswordIn;
import com.hexb.smh.utils.POIExcelHelper;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.SecurityUtils;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;


/**
 * @author : hexb
 */
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AccountService implements IAccountService {

    @NonNull
    final private AccountMapper accountMapper;

    @Override
    public void save(Account account) {
        try {
            Account byLoginName = accountMapper.selectByLoginName(account.getLoginName());
            Assert.notNull(byLoginName, ErrorCodes.DUPLICATE_LOGIN_NAME);

            Account byPhoneNo = accountMapper.selectByLoginName(account.getLoginName());
            Assert.notNull(byPhoneNo, ErrorCodes.DUPLICATE_PHONE_NO);

            account.setCreateBy(AccountHelper.getAccount().getId());
            accountMapper.save(account);
        } catch (DuplicateKeyException e) {
            throw new BusinessException(ErrorCodes.DUPLICATE_LOGIN_NAME_OR_PHONE_NO);
        }
    }

    @Override
    public void update(Account account) {
        Assert.isNull(account, BaseErrorCodes.PARAMETER_IS_NULL);
        Assert.isNull(account.getId(), ErrorCodes.ID_IS_NULL);

        Account byLoginName = accountMapper.selectByLoginName(account.getLoginName());
        Assert.isTrue(byLoginName != null && !byLoginName.getId().equals(account.getId()), ErrorCodes.DUPLICATE_LOGIN_NAME);

        Account byPhoneNo = accountMapper.selectByLoginName(account.getLoginName());
        Assert.isTrue(byPhoneNo != null && !byPhoneNo.getId().equals(account.getId()), ErrorCodes.DUPLICATE_PHONE_NO);

        account.setUpdateBy(AccountHelper.getAccount().getId());
        try {
            accountMapper.updateByEntity(account);
        } catch (DuplicateKeyException e) {
            throw new BusinessException(ErrorCodes.DUPLICATE_LOGIN_NAME_OR_PHONE_NO);
        }
    }

    @Override
    public int delete(Integer id) {
        Assert.isNull(id, BaseErrorCodes.PARAMETER_IS_NULL);
        return accountMapper.delete(id);
    }

    @Override
    public Account findByLoginNameOrPhoneNo(String loginName) {
        return accountMapper.findByLoginNameOrPhoneNo(loginName);
    }


    @Override
    public Account toggleAccount(@NotNull Integer id, @NotNull Boolean enabled) {
        Assert.isNull(id, BaseErrorCodes.PARAMETER_IS_NULL);
        Account account = accountMapper.selectById(id);
        Assert.isNull(account, ErrorCodes.ACCOUNT_NOT_FOUND);
        Assert.isEqual(account.getRole(), RoleType.super_admin, ErrorCodes.CANT_MODIFY_SUPER_ADMIN);

        Account update = new Account();
        update.setId(id);
        update.setEnabled(enabled);
        accountMapper.updateByEntity(update);
        return accountMapper.selectById(id);
    }

    @Override
    public int loginLog(Integer id, String ip) {
        return accountMapper.updateLoginTime(id, ip);
    }

    @Override
    public Page<Account> search(AccountQueryParam param, int pageSize, int pageNum) {
        return Pagination.pageCommonAnyType(param, pageNum, pageSize, accountMapper::search);
    }

    @Override
    public int restPassword(Integer id) {
        Integer accountId = AccountHelper.getAccount().getId();
        Assert.isEqual(id, accountId, ErrorCodes.COULD_NOT_REST_SELF);
        Account account = Account.builder()
                .password(BCrypt.hashpw(SysConfig.DEFAULT_PWD, BCrypt.gensalt(12)))
                .updateBy(accountId)
                .updateTime(new Date())
                .build();
        account.setId(id);
        return accountMapper.updateByEntity(account);
    }

    @Override
    public int active(String newPassword) {
        Assert.isEmpty(newPassword, ErrorCodes.PASSWORD_COULD_NOT_NULL);
        IPasswordIn.assertPwd(newPassword);
        int len = newPassword.length();
        Assert.isFalse(len >= 6 && len <= 20, ErrorCodes.INVALID_PASSWORD_LENGTH);

        String password = BCrypt.hashpw(newPassword, BCrypt.gensalt(12));
        Account account = new Account();
        account.setId(AccountHelper.getAccount().getId());
        account.setPassword(password);
        account.setActivated(true);
        SecurityUtils.getSubject().logout();
        return accountMapper.updateByEntity(account);
    }

    @Override
    public int deleteBatch(Integer[] ids) {
        Assert.isEmpty(ids, BaseErrorCodes.PARAMETER_IS_NULL);
        return accountMapper.deleteBatch(ids);
    }

    @Override
    public int updateEnableBatch(Integer[] ids, Boolean enable) {
        Assert.isEmpty(ids, BaseErrorCodes.PARAMETER_IS_NULL);
        return accountMapper.updateEnableBatch(ids, enable);
    }

    @Override
    public Account findById(Integer id) {
        Assert.isNull(id, BaseErrorCodes.PARAMETER_IS_NULL);
        return accountMapper.selectById(id);
    }

    @Override
    public Account findOneByMap(Map<?, ?> param) {
        return accountMapper.findOneByMap(param);
    }


    @Override
    public int changePassword(String oldPassword, String newPassword) {
        Assert.isNull(newPassword, ErrorCodes.PASSWORD_COULD_NOT_NULL);
        IPasswordIn.assertPwd(newPassword);

        int len = newPassword.length();
        Assert.isFalse(len >= 6 && len <= 20, ErrorCodes.INVALID_PASSWORD_LENGTH);
        Account account = AccountHelper.getAccount();
        String password = account.getPassword();

        boolean check = BCrypt.checkpw(oldPassword, password);
        Assert.isFalse(check, ErrorCodes.INVALID_OLD_PASSWORD);

        Account update = new Account();
        update.setId(account.getId());
        update.setPassword(BCrypt.hashpw(newPassword, BCrypt.gensalt(12)));
        return accountMapper.updateByEntity(update);
    }

    @Override
    public ImportResult<Account> importSalesman(MultipartFile file) {
        ImportResult<Account> res = new ImportResult<>();
        if (null == file) return res;
        String fileName = file.getOriginalFilename();
        try (InputStream is = file.getInputStream()) {
            List<Account> accounts = POIExcelHelper.read(is, r -> {
                AccountParam.AccountParamInsert aa = new AccountParam.AccountParamInsert();
                aa.setLoginName(POIExcelHelper.getCellValue(r.getCell(0)));
                aa.setPhoneNo(POIExcelHelper.getCellValue(r.getCell(1)));
                aa.setName(POIExcelHelper.getCellValue(r.getCell(2)));
                aa.setRemarks(POIExcelHelper.getCellValue(r.getCell(4)));
                Account a = aa.toSalesman();
                a.setEnabled("是".equals(POIExcelHelper.getCellValue(r.getCell(3))));
                return a;
            }, fileName);

            if (CollectionUtils.isNotEmpty(accounts)) {
                res.setTotal(accounts.size());
                accounts.forEach(a -> {
                    try {
                        this.save(a);
                    } catch (Exception e) {
                        res.addFailed(a, e.getMessage());
                    }
                });
            }
        } catch (IOException e) {
            res.setMessage("读取文件出错:" + e.getMessage());
            log.error("读取业务员文件出错", e);
        }
        return res;
    }

    @Override
    public List<SimpleAccount> find(String searchKey, Boolean all) {
        if (!all) {
            return accountMapper.allAdmin(searchKey);
        }
        return accountMapper.allAccount(searchKey);
    }
}
